/* 
 * UCLA CS 111 - Fall 2007 - Lab 1
 * Skeleton code for Lab 1 - Shell processing
 * This file contains skeleton code for executing commands parsed in part A.
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "cmdline.h"
#include "ospsh.h"

/** 
 * Reports the creation of a background job in the following format:
 *  [job_id] process_id
 * to stderr.
 */
#define  MAX_JOBS 50
void report_background_job(int job_id, int process_id);

int assign_job(pid_t pid);
int get_pid(int job_id);
int get_job_id(pid_t pid);
void reset_job(int job_id);
int jobs[MAX_JOBS];
command_t * after_cmd_list[MAX_JOBS];

void initialize()
{
	int i = 0;
	for (i = 0; i < MAX_JOBS; i ++) {
		jobs[i] = 0;
		}

}


/* command_exec(cmd, pass_pipefd)
 *
 *   Execute the single command specified in the 'cmd' command structure.
 *
 *   The 'pass_pipefd' argument is used for pipes.
 *   On input, '*pass_pipefd' is the file descriptor used to read the
 *   previous command's output.  That is, it's the read end of the previous
 *   pipe.  It equals STDIN_FILENO if there was no previous pipe.
 *   On output, command_exec should set '*pass_pipefd' to the file descriptor
 *   used for reading from THIS command's pipe.
 *   If this command didn't have a pipe -- that is, if cmd->commandop != PIPE
 *   -- then it should set '*pass_pipefd = STDIN_FILENO'.
 *
 *   Returns the process ID of the forked child, or < 0 if some system call
 *   fails.
 *
 *   You must also handle the internal commands "cd" and "exit".
 *   These are special because they must execute in the shell process, rather
 *   than a child.  (Why?)
 *
 *   However, these special commands still have a status!
 *   For example, "cd DIR" should return status 0 if we successfully change
 *   to the DIR directory, and status 1 otherwise.
 *   Thus, "cd /tmp && echo /tmp exists" should print "/tmp exists" to stdout
 *   iff the /tmp directory exists.
 *   Not only this, but redirections should work too!
 *   For example, "cd /tmp > foo" should create an empty file named 'foo';
 *   and "cd /tmp 2> foo" should print any error messages to 'foo'.
 *
 *   How can you return a status, and do redirections, for a command executed
 *   in the parent shell?
 *   Hint: It is easiest if you fork a child ANYWAY!
 *   You should divide functionality between the parent and the child.
 *   Some functions will be executed in each process.
 */
static pid_t
command_exec(command_t *cmd, int *pass_pipefd)
{
	pid_t pid = -1;		// process ID for child
	int pipefd[2];		// file descriptors for this process's pipe

	
	int status;

	int fd;

	int ret;


	// DEBUG
	int runPipe;

	runPipe = 1;
	
	/* EXERCISE: Complete this function!
	 * We've written some of the skeleton for you, but feel free to
	 * change it.
	 */


	

	// Create a pipe, if this command is the left-hand side of a pipe.


	// Return -1 if the pipe fails.
	if (cmd->controlop == CMD_PIPE  && runPipe == 1) {
	  
	  if( pipe(pipefd) < 0 ) return -1;
	  
	  if(cmd->argv[0] == NULL) goto error;
	  
	  //*pass_pipefd = pipefd[1];
	
	}

	if(*pass_pipefd != STDIN_FILENO)
	  {
	    dup2(*pass_pipefd, STDIN_FILENO);

	    close(*pass_pipefd);

	  }else {
	    *pass_pipefd = STDIN_FILENO;

	}
	


	

	// Fork the child and execute the command in that child.
	// You will handle all redirections by manipulating file descriptors.
	//
	// This section is fairly long.  It is probably best to implement this
	// part in stages, checking it after each step.  For instance, first
	// implement just the fork and the execute in the child.  This should
	// allow you to execute simple commands like 'ls'.  Then add support
	// for redirections: commands like 'ls > foo' and 'cat < foo'.  Then
	// add parentheses, then pipes, and finally the internal commands
	// 'cd' and 'exit'.
	//
	// In the child, you should:
	//    1. Set up stdout to point to this command's pipe, if necessary.
	//    2. Set up stdin to point to the PREVIOUS command's pipe (that
	//       is, *pass_pipefd), if appropriate.
	//    3. Close some file descriptors.  Hint: Consider the read end
	//       of this process's pipe.
	//    4. Set up redirections.
	//       Hint: For output redirections (stdout and stderr), the 'mode'
	//       argument of open() should be set to 0666.
	//    5. Execute the command.
	//       There are some special cases:
	//       a. Parentheses.  Execute cmd->subshell.  (How?)
	//       b. A null command (no subshell, no arguments).
	//          Exit with status 0.
	//       c. "exit".
	//       d. "cd".
	//

	// Figure out special commands

	  char* fileName = cmd->argv[0];
	 

	  
	
	// "cd" error note:
	// 	- Upon syntax errors: Display the message
	//	  "cd: Syntax error on bad number of arguments"
	// 	- Upon system call errors: Call perror("cd")
	//
	// "cd" Hints:
	//    For the "cd" command, you should change directories AFTER
	//    the fork(), not before it.  Why?
	//    Design some tests with 'bash' that will tell you the answer.
	//    For example, try "cd /tmp ; cd $HOME > foo".  In which directory
	//    does foo appear, /tmp or $HOME?  If you chdir() BEFORE the fork,
	//    in which directory would foo appear, /tmp or $HOME?
	//
	//    EXTRA CREDIT: Our "cd" solution changes the
	//    directory both in the parent process and in the child process.
	//    This introduces a potential race condition.
	//    Explain what that race condition is, and fix it.
	//    Hint: Investigate fchdir().




	  // Special Command: exit
		
	  if(cmd->argv[0]!=NULL && strcmp(cmd->argv[0],"exit") == 0  )
	    {
	      _exit(0);
	      
	    }

	  if(cmd->argv[0] == NULL && cmd->subshell == NULL)
	     	// NULL Command: exit with status 0
		exit(0);

	 

	  	    
	// Sam
	//Speacial Command: after
	if(fileName != NULL && strcmp(fileName, "after") == 0){
		// catch syntax errors	
		if (cmd->argv[1] == NULL ) {
			fprintf(stderr, "after: Syntax error\n");
			return -1;
		}	
		
		char * arg1 = cmd->argv[1]; // get the argument
		int job_id = 0;
		while(arg1 != NULL && *arg1 != '\0' && *arg1 != '\n' && !isspace(*arg1)) {
			if(!isdigit(*arg1)) {
				fprintf(stderr, "after: Syntax error\n");
				return -1;
			}			
			job_id = job_id * 10 + (*arg1) - 48;
			arg1 ++;
		}

		
		
		
		// build a new cmd using the arguments of after
		char * new_cmd_info = malloc(BUFSIZ);
		char * space;
		int i;
		command_t * new_cmd;
		space = " ";

		if(!new_cmd_info)
		{
			fprintf(stderr, "Did not have enough memory\n");
			return -1;
		}
		new_cmd_info = cmd->argv[2];
		
		i = 3;
		while(cmd->argv[i]) {
			
			new_cmd_info = strcat(new_cmd_info,  space);
			new_cmd_info = strcat(new_cmd_info,  cmd->argv[i]);
			i++;
		}
		
		new_cmd_info = strcat(new_cmd_info, "\0");
	
		parsestate_t parsestate;
		
		parse_init(&parsestate, new_cmd_info);

		new_cmd = command_parse(&parsestate);
		
		//printf("%s\n", new_cmd->argv[0]);
	//	return -1;
		if(!new_cmd)
		{
			fprintf(stderr, "Out of memory!\n");
			free(new_cmd_info);
			return -1;
		}
		new_cmd->next = after_cmd_list[job_id]; //append the original after_cmd_list to our current cmd
	  new_cmd->controlop = CMD_SEMICOLON; // assign the appropriate controlop: cmd->next will execute regardless of the exit status of cmd
		after_cmd_list[job_id] = new_cmd; // assign cmd as the new after_cmd_list for job_id
		
		free(new_cmd_info);
		//command_free(cmdlist);
		return 0;
	}
	//End Sam






	pid = fork();
	

	if(pid < 0) // we have an error
	  return -1;
	else if(pid == 0 ){ // we are the child!
	
	  //printf("CHILD: controlop = %d\n", cmd->controlop);
	  

	  // If we are in a subshell
	  	  
	   if(cmd->argv[0]==NULL && cmd->subshell != NULL  )
	    {
	      int command_status;
	      command_status = command_line_exec(cmd->subshell);

	      return command_status;
	      
	    } 


	     // If we are piping
	   if (cmd->controlop == CMD_PIPE && runPipe == 1) {
	   

	    
	     int dup_status;

	    // STDOUT is really writing to the pipe
	     dup_status = dup2(pipefd[1], STDOUT_FILENO);
	    
	     // printf("dup_status in CHILD: %d",dup_status);

	    if(*pass_pipefd != STDIN_FILENO)			
				dup2(*pass_pipefd,STDIN_FILENO);

	    	     // Close the read fd, we're gonna write
	     close(pipefd[0]);
	    close(pipefd[1]);
	  }
	  



	  // Special command: cd
	 
	  // NOTE: make sure that we dont have an empty command too
	  if(cmd->argv[0]!=NULL && strcmp(fileName, "cd") == 0 )
	  {
	     
	    if(cmd->argv[1] == NULL ) {
	      fprintf(stderr, "cd: Syntax error on bad number of arguments\n");
	      exit(1);				
	    }
	    
	    // call the system call to change directory
	    ret = chdir(cmd->argv[1]);

	    // if the system call threw an error, then perror("cd")
	    if(ret != 0 ){
	      perror("cd");
	      exit(1);
	    }
	    

	   

	     // printf("CHILD: changing directory to %s\n",cmd->argv[1]);
	      
	     // Make sure to exit
	    exit(0);
     
	  }

	  





	   


	 

	  // See if we are going to need redirection
	  if(cmd->redirect_filename[0] != NULL || cmd->redirect_filename[1] != NULL || cmd->redirect_filename[2] != NULL) {
	      
	      // Figure out if we need STDIN
	      
	      // we need to read
	      if( cmd->redirect_filename[0] != NULL){
			fd = open(cmd->redirect_filename[0],O_RDONLY,0666);
		
	
			// check for read errors
			if(fd < 0){
			  perror("No such file or directory\n");
			  goto error;
			}
		// switch up the file descriptors
		dup2(fd, 0);
		
	      }

	      // Figure out if we need STDOUT
	      // we need to write
	      if( cmd->redirect_filename[1] != NULL){
		 fd = open(cmd->redirect_filename[1],O_WRONLY | O_CREAT |  O_TRUNC, 0666);
		 
		// check for read errors
		 if(fd < 0){
		   perror("No such file or directory\n");
		   goto error;
		 }
		 // switch up the file descriptors
		dup2(fd, 1);
	      }

	      
	      // Figure out if we need STDERR
	      // we need to write
	  	 if( cmd->redirect_filename[2] != NULL){
		   fd = open(cmd->redirect_filename[2],O_WRONLY | O_CREAT |  O_TRUNC,0666);

		   // check for read errors
		   if(fd < 0){
		     perror("No such file or directory\n");
		     goto error;
		   }

		   // switch up the file descriptors
		   dup2(fd, 2);
		   
		 }

	      close(fd);

	    }

	  if(execvp(fileName, cmd->argv) < 0) 
	    exit(1);
	
	}else {


	  // We are the parent!
	  
	 

	  // wait for the child.
	  // waitpid(pid, &status, 0);


	  
	  

	  // If we are piping
	  if (cmd->controlop == CMD_PIPE  && runPipe == 1) {
	    // Close the write fd because we are reading
	    close(pipefd[1]);

	    //   dup2(*pass_pipefd, pipefd[0] );
	     int dup_status;

	    dup_status = dup2(pipefd[0], *pass_pipefd);
	    //*pass_pipefd = pipefd[0];
	    //  printf("dup_status in PARENT: %d\n",dup_status);


	    close(pipefd[0]);
	  }
	  
	  

	  
	    if(cmd->argv[0]!=NULL && strcmp(fileName, "cd") == 0 )
	    {
	      
	      ret = chdir(cmd->argv[1]);
	      
	      if(ret == 0 ) return 0;
	      else {
		// Check to make sure that there is only one argument.
		if(cmd->argv[2] != NULL)
		{
		  // Sam : I edited the error statment. Used fprintf instead since it's an error  "
		  fprintf(stderr, "cd: Syntax error on bad number of arguments\n");

		    return -1;
		}
		
		
	      }

	    }
	 
	  
	}




	// In the parent, you should:
	//    1. Close some file descriptors.  Hint: Consider the write end
	//       of this command's pipe, and one other fd as well.
	//    2. Handle the special "exit" and "cd" commands.
	//    3. Set *pass_pipefd as appropriate.
	//


	/* Your code here. */




	// return the child process ID
	return pid;

 error:
	
	return -1;


}


/* command_line_exec(cmdlist)
 *
 *   Execute the command list.
 *
 *   Execute each individual command with 'command_exec'.
 *   String commands together depending on the 'cmdlist->controlop' operators.
 *   Returns the exit status of the entire command list, which equals the
 *   exit status of the last completed command.
 *
 *   The operators have the following behavior:
 *
 *      CMD_END, CMD_SEMICOLON
 *                        Wait for command to exit.  Proceed to next command
 *                        regardless of status.
 *      CMD_AND           Wait for command to exit.  Proceed to next command
 *                        only if this command exited with status 0.  Otherwise
 *                        exit the whole command line.
 *      CMD_OR            Wait for command to exit.  Proceed to next command
 *                        only if this command exited with status != 0.
 *                        Otherwise exit the whole command line.
 *      CMD_BACKGROUND, CMD_PIPE
 *                        Do not wait for this command to exit.  Pretend it
 *                        had status 0, for the purpose of returning a value
 *                        from command_line_exec.
 */ 

int
command_line_exec(command_t *cmdlist)
{
	int cmd_status = 0;	    // status of last command executed
	int pipefd = STDIN_FILENO;  // read end of last pipe
	pid_t child_id = -1; // the pid of the child
	//abort();
	int job_id;
	while (cmdlist) {
		int wp_status;	    // Hint: use for waitpid's status argument!
				    // Read the manual page for waitpid() to
				    // see how to get the command's exit
				    // status (cmd_status) from this value.
		
		// EXERCISE: Fill out this function!
		// If an error occurs in command_exec, feel free to abort().
		
		// run the cmdlist
		child_id = command_exec(cmdlist, &pipefd);
		
		switch (cmdlist->controlop) {
			case CMD_END:             // end of command line
			case CMD_SEMICOLON:      // ';'; Same implementation as CMD_END
				
				// wait till child is done				
				waitpid(child_id, &cmd_status, 0);

				// go to next command
				cmdlist = cmdlist->next;
				break;
			case CMD_AND:            // '&&'
				// wait till child is done				
				if(waitpid(child_id, &cmd_status, 0) > 0)
				{
					if(WEXITSTATUS(cmd_status) == 0)
						cmdlist = cmdlist->next;
					else
						goto done;
				}				
				else
					goto done;
				break;
			case CMD_OR:  
							
				// wait till child is done				
				if(waitpid(child_id, &cmd_status, 0) > 0)
				{
					if(WEXITSTATUS(cmd_status) != 0)
						cmdlist = cmdlist->next;
					else
						goto done;;
				}				
				else
					goto done;
				break;
			case CMD_BACKGROUND:     // '&'
				
				job_id = assign_job(child_id);
				
				// throw an error if there is no more available job_id			
				if(job_id < 0)
				{
					fprintf(stderr, "Error: No more available job id!\n");
					return 1;
				}

				//report background job
				report_background_job(job_id, child_id);
				
				// if the job has finished, then run all the after_cmd that's associated with the child
				if( waitpid(child_id, &cmd_status, WNOHANG) > 0 )
					if(job_id > 0)// if there was such job
					{					
						// execute all the AFTER commands for this job
						command_line_exec(after_cmd_list[job_id]); 
						reset_job(job_id); // reset the job when done
					
					}
				
					
				// go to next command
				cmdlist = cmdlist->next;
				break;

			case CMD_PIPE:                // '|'
				// gets the exit status but does not wait till the child is done				
				waitpid(child_id, &cmd_status, WNOHANG);
				
				// go to next command
				cmdlist = cmdlist->next;
				break;
		}
		
	}

done:
	return cmd_status;
}



/* this command runs when we get a SIGCHLD signal*/
void run_after_cmd(int sig)
{
	if(sig == SIGCHLD)
	{
	int i; // counter for the for-loop
	int wp_status;	// 2nd arg for waitpid
	int exit_status;	// return value of waitpid; 
										//also used to get the pid of the terminated child
//	printf("%d process ended yay \n", jobs[1]);

	// traverse through all the jobs
	
	for(i = 1;  i< MAX_JOBS; i ++) {
			
		int temp = waitpid(jobs[1], &exit_status, WNOHANG);
		// printf("Oh dear, something went wrong with waitpid()! %s\n", strerror(errno));
		//printf("%d %d\n", waitpid(jobs[1], &exit_status, WNOHANG), temp);
		if (i == 1 ){
		//printf("temp %d %d\n", waitpid(jobs[1], &exit_status, WNOHANG), temp);
		//temp = jobs[1];

		}
		if( temp > 0 )// if the job has finished
		{
			
				//printf("this is the after command %s yaya \n", after_cmd_list[1]->argv[0]);
				//printf("%s is the command yayy \n", after_cmd_list[1]->argv[0]);
			
			command_line_exec(after_cmd_list[i]); // execute all the AFTER commands for this job
			
			reset_job(i); // reset the job when done
			
		}
		
		// free the child process
	}
	}
	return;
}

/* resets the job (id = job_id)*/
void reset_job(int job_id)
{
	jobs[job_id] = 0;  // reset the pid to 0
	command_free(after_cmd_list[job_id]);
	after_cmd_list[job_id] = NULL; // reset the AFTER command list to NULL
}

/* assigns pid to the first available job_id
* then return the job_id*/
int assign_job( pid_t pid) 
{
	int job_id = 0;

	// if there is a job with such pid, return the job_id
	job_id = get_job_id (pid);

	if(job_id > 0)
		return job_id;
	
	job_id = 1; // reset job_id to search for an open spot
	// find the first available job_id
	while(job_id < MAX_JOBS) {
		if(jobs[job_id] == 0) {
			jobs[job_id] = pid;
			return job_id;
		}
		job_id++;

	}
	// if there is no more spots available, return -1
	return -1;

}

/* returns the pid associated with job_id*/
int get_pid(int job_id)
{
  int ans = jobs[job_id];
	return	ans; 
}

/* returns the job_id associated with pid*/
int get_job_id (pid_t pid)
{
	int i; // counter for the for-loop
	// traverse through all the jobs
	for(i = 1;  i< MAX_JOBS; i ++)
		if(jobs[i] == pid) 
			return i; // if pid is found, return the job_id
	return -1; // if pid is not found, return -1;
}

void report_background_job(int job_id, int process_id) {
    fprintf(stderr, "[%d] %d\n", job_id, process_id);
}
